﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.IdentityModel.Claims;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.Xml;
using SharpShare.Configuration;
using SharpShare.Diagnostics;
using SharpShare.Storage;

namespace SharpShare.Management {
    public enum ShareManagerState {
        Stopped,
        Started,
        Stopping,
        Starting
    }

    [ServiceBehavior(
        InstanceContextMode = InstanceContextMode.Single,
        ConcurrencyMode = ConcurrencyMode.Multiple,
        IncludeExceptionDetailInFaults = true
    )]
    public class ShareManager : IShareManager, IConfigurationContext, ILogProvider {
        private Dictionary<string, IShareService> _services = new Dictionary<string, IShareService>();
        private Dictionary<string, IStorageProvider> _storageProviders = new Dictionary<string, IStorageProvider>();
        private ServiceHost _serviceHost;
        private System.Configuration.Configuration _configuration;

        public ShareManager() {
            // Create service and storage provider factories using reflection.
            Uri address = new Uri("net.pipe://localhost/sharpshare");
            ServiceHost host = new ServiceHost(this, address);

            var endpoint =
            host.AddServiceEndpoint(
                typeof(IShareManager),
                new NetNamedPipeBinding(NetNamedPipeSecurityMode.None) {
                    MaxReceivedMessageSize = int.MaxValue,
                    ReaderQuotas = new System.Xml.XmlDictionaryReaderQuotas() {
                        MaxStringContentLength = int.MaxValue,
                        MaxNameTableCharCount = int.MaxValue,
                        MaxDepth = int.MaxValue,
                        MaxBytesPerRead = int.MaxValue,
                        MaxArrayLength = int.MaxValue
                    }
                }, address);

            host.Open();

            //var c = ShareManager.CreateRemoteManager();
            //var con = c.GetConfiguration();

            _serviceHost = host;
        }

        public ShareManagerState State { get; private set; }

        public ServiceHost ServiceHost { get { return _serviceHost; } }

        public System.Configuration.Configuration Configuration {
            get {
                if (_configuration == null) {
                    _configuration = System.Configuration.ConfigurationManager.OpenExeConfiguration(System.Configuration.ConfigurationUserLevel.None);
                }

                return _configuration;
            }
            set {
                this.DemandStopped();
                _configuration = value;
            }
        }

        private SharpShareSectionGroup SharpShareConfiguration {
            get {
                if (this.Configuration == null) return null;
                return (SharpShareSectionGroup)this.Configuration.GetSectionGroup("sharpShare");
            }
        }

        public ReadOnlyCollection<IShareService> Servers {
            get {
                return _services.Values.ToList().AsReadOnly();
            }
        }

        public void Start() {
            this.DemandStopped();

            if (this.SharpShareConfiguration == null) {
                throw new InvalidOperationException("No SharpShare configuration was found.");
            }

            Log.Add(this, EntryType.Information, "Starting services...");

            this.State = ShareManagerState.Starting;

            try {
                UpdateLog();
                UpdateServices();

                foreach (IShareService server in this.Servers) {
                    server.Start();
                }
            } catch (Exception ex) {
                Log.Add(this, EntryType.Error, "Unable to start services: {0}", ex);
                Stop();
                throw;
            }

            this.State = ShareManagerState.Started;

            Log.Add(this, EntryType.Information, "Services started.");
        }

        public void Stop() {
            this.DemandRunning();

            Log.Add(this, EntryType.Information, "Stopping services...");

            this.State = ShareManagerState.Stopping;

            this.UpdateLog();

            foreach (IShareService server in this.Servers) {
                if (server.State == ShareServiceState.Running) {
                    server.Stop();
                }
            }

            this.State = ShareManagerState.Stopped;

            Log.Add(this, EntryType.Information, "Services stopped.");
        }

        private void UpdateServices() {
            _storageProviders.Clear();
            _services.Clear();

            foreach (ServiceElement serviceElement in this.SharpShareConfiguration.Services) {
                IShareService service = serviceElement.CreateService(this);
                _services[serviceElement.Name] = service;

                foreach (ShareElement shareElement in serviceElement.Shares) {
                    string storageProviderName = (string.IsNullOrWhiteSpace(shareElement.StorageProvider) ? shareElement.Name : shareElement.StorageProvider);
                    IStorageProvider storageProvider = ((IConfigurationContext)this).GetStorageProvider(storageProviderName);

                    storageProvider = shareElement.GetShareStorageProvider(storageProvider, this);

                    service.AddShare(storageProvider);

                    shareElement.ConfigureService(service, storageProvider, this);
                }
            }
        }

        private void UpdateLog() {
            var value = this.SharpShareConfiguration;

            if ((this.State == ShareManagerState.Started || this.State == ShareManagerState.Starting)
                && value != null && value.Diagnostic != null && value.Diagnostic.Log != null && value.Diagnostic.Log.Enabled) {
                string logPath = value.Diagnostic.Log.Location;
                System.IO.Directory.CreateDirectory(logPath);
                string logName = System.IO.Path.Combine(logPath, DateTime.Now.Ticks.ToString() + ".htm");
                SharpShare.Diagnostics.Log.HtmlWriter = new System.IO.StreamWriter(logName);
            } else {
                var oldWriter = SharpShare.Diagnostics.Log.HtmlWriter;
                SharpShare.Diagnostics.Log.HtmlWriter = null;

                if (oldWriter != null) {
                    oldWriter.Dispose();
                }
            }

        }

        private void DemandRunning() {
            if (this.State != ShareManagerState.Started && this.State != ShareManagerState.Starting) throw new InvalidOperationException("Manager must be running.");
        }
        private void DemandStopped() {
            if (this.State != ShareManagerState.Stopped) throw new InvalidOperationException("Manager must not be running.");
        }

        #region IShareManager Members

        void IShareManager.Start() {
            if (this.State != ShareManagerState.Stopped)
                return;

            try {
                this.Start();
            } catch (Exception ex) {
                throw new FaultException<ApplicationException>(new ApplicationException(ex.Message), ex.Message);
            }
        }

        ReadOnlyCollection<Data.SessionInfo> IShareManager.Sessions {
            get {
                var infos = _services.Values
                    .SelectMany(g => g.Sessions, (service, session) => {
                        Data.SessionInfo info = new Data.SessionInfo(
                            session.GetHashCode().ToString(),
                            service.Name,
                            session.User) {
                                Date = session.Date
                            };

                        var fileInfos = session.OpenFiles
                            .Select(o => new Data.OpenFileInfo(o.Identifier, o.File.CreateNamePath()) {
                                AccessMode = o.AccessMode,
                                BytesRead = o.BytesRead,
                                BytesWritten = o.BytesWritten,
                                DateLastAccessed = o.DateLastAccessed,
                                DateOpened = o.DateOpened
                            });

                        foreach (var fileInfo in fileInfos) {
                            info.OpenFiles.Add(fileInfo);
                        }

                        return info;
                    });

                return infos.ToList().AsReadOnly();

            }
        }

        void IShareManager.Stop() {
            if (this.State != ShareManagerState.Started)
                return;

            try {
                this.Stop();
            } catch (Exception ex) {
                throw new FaultException<ApplicationException>(new ApplicationException(ex.Message), ex.Message);
            }
        }

        string IShareManager.GetLocalConfigurationLocation() {
            return this.Configuration.FilePath;
        }

        void IShareManager.UpdateConfiguration() {
            try {
                if (this.State == ShareManagerState.Started) {
                    this.Stop();
                }
                this.Configuration = null;
                this.Start();
            } catch (Exception ex) {
                throw new FaultException<ApplicationException>(new ApplicationException(ex.Message), ex.Message);
            }
        }

        #endregion

        private IEnumerable<Assembly> GetConfigurationAssemblies() {
            var storageProviderSections = this.Configuration
              .SectionGroups["sharpShare"]
              .SectionGroups["storageProviders"]
              .Sections
              .Cast<ConfigurationSection>();
            var serviceSections = this.Configuration
                .SectionGroups["sharpShare"]
                .SectionGroups["services"]
                .Sections
                .Cast<ConfigurationSection>();

            var assemblies = storageProviderSections.Concat(serviceSections)
                .Select(s => Type.GetType(s.SectionInformation.Type).Assembly)
                .Distinct();

            return assemblies;
        }

        public static IShareManager CreateRemoteManager() {
            return CreateRemoteManager(new Uri("net.pipe://localhost/sharpshare"));
        }
        public static IShareManager CreateRemoteManager(Uri addressUri) {
            NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None) {
                MaxReceivedMessageSize = int.MaxValue,
                ReaderQuotas = new System.Xml.XmlDictionaryReaderQuotas() {
                    MaxStringContentLength = int.MaxValue,
                    MaxNameTableCharCount = int.MaxValue,
                    MaxDepth = int.MaxValue,
                    MaxBytesPerRead = int.MaxValue,
                    MaxArrayLength = int.MaxValue
                }
            };

            EndpointAddress address = new EndpointAddress(addressUri);
            IShareManager manager = ChannelFactory<IShareManager>.CreateChannel(binding, address);
            return manager;
        }


        #region IConfigurationContext Members

        IStorageProvider IConfigurationContext.GetStorageProvider(string name) {
            if (!_storageProviders.ContainsKey(name)) {
                StorageProviderElement element = this.SharpShareConfiguration.StorageProviders[name];

                if (element == null)
                    throw new InvalidOperationException(string.Format("Unable to find storage provider with name '{0}'.", name));

                IStorageProvider provider = element.CreateStorageProvider(this);

                _storageProviders[name] = provider;

                return provider;
            }

            return _storageProviders[name];
        }

        #endregion


        #region ILogProvider Members

        string ILogProvider.Name {
            get { return "Share Manager"; }
        }

        #endregion
    }

    public class NetDataContractOperationBehavior : DataContractSerializerOperationBehavior {
        public NetDataContractOperationBehavior(OperationDescription operation)
            : base(operation) {
        }

        public NetDataContractOperationBehavior(
                    OperationDescription operation,
                    DataContractFormatAttribute dataContractFormatAttribute)
            : base(operation, dataContractFormatAttribute) {
        }

        public override XmlObjectSerializer CreateSerializer(
                    Type type, string name, string ns, IList<Type> knownTypes) {
            return new NetDataContractSerializer(name, ns);
        }

        public override XmlObjectSerializer CreateSerializer(
                    Type type, XmlDictionaryString name, XmlDictionaryString ns,
                    IList<Type> knownTypes) {
            return new NetDataContractSerializer(name, ns);
        }
    }

    public class UseNetDataContractSerializerAttribute : Attribute, IOperationBehavior {
        public void AddBindingParameters(OperationDescription description,
                                         BindingParameterCollection parameters) {
        }

        public void ApplyClientBehavior(OperationDescription description,
                                        System.ServiceModel.Dispatcher.ClientOperation proxy) {
            ReplaceDataContractSerializerOperationBehavior(description);
        }

        public void ApplyDispatchBehavior(OperationDescription description,
                                          System.ServiceModel.Dispatcher.DispatchOperation dispatch) {
            ReplaceDataContractSerializerOperationBehavior(description);
        }

        public void Validate(OperationDescription description) {
        }

        private static void ReplaceDataContractSerializerOperationBehavior(
                    OperationDescription description) {
            DataContractSerializerOperationBehavior dcsOperationBehavior =
            description.Behaviors.Find<DataContractSerializerOperationBehavior>();

            if (dcsOperationBehavior != null) {
                description.Behaviors.Remove(dcsOperationBehavior);
                description.Behaviors.Add(new NetDataContractOperationBehavior(description));
            }
        }
    }

}
